package besta.moon.util;

import java.util.zip.Checksum;

public class HashUtils {

    private final Murmur3A hash = new Murmur3A(42);

    public int hash(byte[] bytes) {
        hash.reset();
        hash.update(bytes);
        return hash.getIntValue() & 0x7fffffff;
    }

    private static class Murmur3A implements Checksum {

        private static final int C1 = 0xcc9e2d51;
        private static final int C2 = 0x1b873593;

        private final int seed;

        private int h1;
        private int length;

        private int partialK1;
        private int partialK1Pos;

        public Murmur3A(int seed) {
            this.seed = seed;
            h1 = seed;
        }

        @Override
        public void update(int b) {
            switch (partialK1Pos) {
                case 0 -> {
                    partialK1 = 0xff & b;
                    partialK1Pos = 1;
                }
                case 1 -> {
                    partialK1 |= (0xff & b) << 8;
                    partialK1Pos = 2;
                }
                case 2 -> {
                    partialK1 |= (0xff & b) << 16;
                    partialK1Pos = 3;
                }
                case 3 -> {
                    partialK1 |= (0xff & b) << 24;
                    applyK1(partialK1);
                    partialK1Pos = 0;
                }
            }
            length++;
        }

        @Override
        public void update(byte[] b, int off, int len) {
            while (partialK1Pos != 0 && len > 0) {
                update(b[off]);
                off++;
                len--;
            }

            int remainder = len & 3;
            int stop = off + len - remainder;
            for (int i = off; i < stop; i += 4) {
                int k1 = getIntLE(b, i);
                applyK1(k1);
            }
            length += stop - off;

            for (int i = 0; i < remainder; i++) {
                update(b[stop + i]);
            }
        }

        @Override
        public void update(byte[] b) {
            update(b, 0, b.length);
        }

        private void applyK1(int k1) {
            k1 *= C1;
            k1 = (k1 << 15) | (k1 >>> 17); 
            k1 *= C2;

            h1 ^= k1;
            h1 = (h1 << 13) | (h1 >>> 19);  
            h1 = h1 * 5 + 0xe6546b64;
        }

        @Override
        public long getValue() {
            return 0xFFFFFFFFL & getIntValue();
        }

        public int getIntValue() {
            int finished = h1;
            if (partialK1Pos > 0) {
                int k1 = partialK1 * C1;
                k1 = (k1 << 15) | (k1 >>> 17); 
                finished ^= k1;
            }
            finished ^= length;
            finished ^= finished >>> 16;
            finished *= 0x85ebca6b;
            finished ^= finished >>> 13;
            finished *= 0xc2b2ae35;
            finished ^= finished >>> 16;

            return finished;
        }

        @Override
        public void reset() {
            h1 = seed;
            length = 0;
            partialK1Pos = 0;
        }

        private int getIntLE(byte[] bytes, int index) {
            return (bytes[index] & 0xff) | ((bytes[index + 1] & 0xff) << 8)
                    | ((bytes[index + 2] & 0xff) << 16) | (bytes[index + 3] << 24);
        }
    }
}
